package com.itheima.d2_reflect;

import org.junit.Test;

import java.lang.reflect.Method;

/**
 * 目标：掌握获取类的成员方法，并对其进行操作。
 *
 * 使用反射技术获取方法对象并使用
 *      第一步：获得class对象       第二步：获得Method对象      第三步：运行方法
 *
 *
 *      方法
 *  Method[] getMethods()                                               返回类的所有成员方法对象的数组（只能拿public的）
 *  Method[] getDeclaredMethods()                                       返回类的所有成员方法对象的数组，存在就能拿到
 *  Method getMethod(String name, Class<?>... parameterTypes)           返回类的单个成员方法对象（只能拿public的）
 *  Method getDeclaredMethod(String name, Class<?>... parameterTypes)   返回类的单个成员方法对象，存在就能拿到。第二个形参是可变参数
 *
 * 使用反射技术获取方法对象的作用：获取成员方法的作用依然是在某个对象中进行执行此方法
 *
 * Method类中用于触发执行的方法
 *              方法：                                     说明：
 * Object invoke(Object obj, Object... args)参数一：用obj对象调用该方法。参数二：调用方法的传递的参数（如果没有就不写）。返回值：方法的返回值（如果没有就不写）
 * public void setAccessible(boolean flag)  设置为true，表示禁止检查访问控制（暴力反射）
 *
 *
 * 2025年9月9日10:39:19：这次磊哥视频没有讲解这块内容，视频里面的ppt也没有这块内容，和现在公开的ppt不一样：
 *作用、应用场景
 * 反射的作用-绕过编译阶段为集合添加数据
 * 反射是作用在运行时的技术，此时集合的泛型将不能产生约束了，此时是可以为集合存入其他任意类型的元素的。
 * ArrayList<Integer> list = new ArrayList<>();
 * list.add(100);
 * // list.add(“黑马"); //报错
 * list.add(99);
 *泛型只是在编译阶段可以约束集合只能操作某种数据类型，在编译成Class文件进入运行阶段的时候，其真实类型都是ArrayList了，泛型相当于被擦除了。
 *
 * 反射为何可以给约定了泛型的集合存入其他类型的元素？
 * 编译成Class文件进入运行阶段的时候，泛型会自动擦除。
 * 反射是作用在运行时的技术，此时已经不存在泛型了。
 *
 *
 */
public class Test4Method {
    @Test
    public void testGetMethods() throws Exception {
        //  1、反射第一步：先得到Class对象。
        Class c = Cat.class;
        // 2、获取类的全部成员方法。用method类型的数组接收
        Method[] methods = c.getDeclaredMethods();
        // 3、遍历这个数组中的每个方法对象
        for (Method method : methods) {
            //得到方法的名字，得到方法的参数个数，获取方法的返回值类型
            System.out.println(method.getName() + "--->"
                    + method.getParameterCount() + "---->"
                    + method.getReturnType());
        }

        System.out.println("--------------通过反射获取类中的成员方法，获取方法对象----------------");

        //  4、获取某个方法对象
        Method run = c.getDeclaredMethod("run"); // 拿无参数的run方法，异常直接抛出去
        System.out.println(run.getName() + "--->"
                + run.getParameterCount() + "---->"
                + run.getReturnType());

        System.out.println("--------------------------------");

        Method eat = c.getDeclaredMethod("eat", String.class);  //填参数类型的声明，才知道想取的是哪个重载的方法
        System.out.println(eat.getName() + "--->"
                + eat.getParameterCount() + "---->"
                + eat.getReturnType());

        System.out.println("---------------执行获取的成员方法-----------------");

/*        Object invoke(Object obj, Object... args)  触发某个对象的该方法执行
        首先要传一个对象给invoke方法，因为获得的run成员方法对象所代表的run成员方法，是对象的方法。
         所以要传一个对象来触发执行，代表的就是要执行哪个对象的该方法，所以要创建一个对象。
         第二个形参是可变参数，是用来声明这个方法需要接收的数据的
         把触发方法的对象和方法需要的参数写好之后，才会真正的把方法执行，执行完之后会返回这个方法的结果，返回结果是用object类型返回的
         无返回结果的话，就返回一个null
         并且要调用的run方法时private，所以会报权限服务异常的错，所以也要用暴力反射
         */
        Cat cat = new Cat();
        run.setAccessible(true); // 禁止检查访问权限
        Object rs = run.invoke(cat); // 调用无参数的run方法，用cat对象触发调用的。
        System.out.println("该方法无返回结果，返回一个null： "+rs);

        eat.setAccessible(true); // 禁止检查访问权限
        String rs2 = (String) eat.invoke(cat, "鱼儿");
        System.out.println(rs2);
    }
}
